package com.salmon.run.js;

import org.junit.Test;

import javax.script.*;
import java.io.File;
import java.util.*;

public class RunJS {

    /**
     * 打印出当前的 JDK 所支持的所有脚本引擎
     */
    @Test
    public void supportScript() {
        ScriptEngineManager factory = new ScriptEngineManager();
        for (ScriptEngineFactory available : factory.getEngineFactories()) {
            System.out.println(available.getEngineName());
            // 打印脚本具体名称信息
            System.out.println(available.getNames());
        }

        //首先需要实例化一个工厂 ： ScriptEngineManager
        //ScriptEngineManager factory = new ScriptEngineManager();
        //ScriptEngineManager 将在 Thread Context ClassLoader 的 Classpath 中根据 jar 文件的 META-INF 来查找可用的脚本引擎。
        // 它提供了 3 种方法来检索脚本引擎：
        // create engine by name
        // 1. ScriptEngine engine = factory.getEngineByName ("JavaScript");
        // create engine by ext name
        // 2. ScriptEngine engine = factory.getEngineByExtension ("js");
        // create engine by mime type name
        // 3. ScriptEngine engine = factory.getEngineByMimeType ("application/javascript");
    }

    @Test
    public void test1() throws Exception {
        ScriptEngineManager factory = new ScriptEngineManager();
        ScriptEngine engine = factory.getEngineByName ("JavaScript");
        engine.eval("print('Hello World')");
    }

    @Test
    public void test2() {
        // create a script engine manager
        ScriptEngineManager factory = new ScriptEngineManager();
        // create a JavaScript engine
        ScriptEngine engine = factory.getEngineByName("JavaScript");
        try {
            engine.eval("function max_num(a,b){return (a>b)?a:b;}");
            Invocable invoke = (Invocable) engine;
            Object maxNum = invoke.invokeFunction("max_num", 4, 6);
            System.out.println(maxNum);
            maxNum = invoke.invokeFunction("max_num", 7, 6);
            System.out.println(maxNum);
        } catch (Exception e) {
            // TODO: handle exception
        }
    }

    /**
     * 　脚本引擎默认是解释执行的，如果需要反复执行脚本，
     *   可以使用它的可选接口Compilable来编译执行脚本，以获得更好的性能
     */
    @Test
    public void test3() {
        try {
            ScriptEngineManager factory = new ScriptEngineManager();
            ScriptEngine engine = factory.getEngineByName ("JavaScript");
            Compilable compEngine = (Compilable) engine;
            CompiledScript script = compEngine
                    .compile("function max_num(a,b){return (a>b)?a:b;}");
            script.eval();
            Invocable invoke = (Invocable) engine;
            Object maxNum = invoke.invokeFunction("max_num", 4, 6);
            System.out.println(maxNum);
            maxNum = invoke.invokeFunction("max_num", 10, 6);
            System.out.println(maxNum);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    @Test
    public void test5() {
        String js = "function decodeToString(payload) {" +
                "return payload" +
                "}";
        try {
            ScriptEngineManager factory = new ScriptEngineManager();
            ScriptEngine engine = factory.getEngineByName ("JavaScript");
            engine.eval(js);
            Invocable invoke = (Invocable) engine;
            Map<String, String> aa = new HashMap<>();
            aa.put("q", "12");
            aa.put("c", "34");
            // {q=12, c=34}
            Object maxNum = invoke.invokeFunction("decodeToString", aa);
            System.out.println(maxNum);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    @Test
    public void test6() {
        String js = "function decodeToString(payload) {" +
                "return String.fromCharCode.apply(String, payload);" +
                "}";
        try {
            ScriptEngineManager factory = new ScriptEngineManager();
            ScriptEngine engine = factory.getEngineByName ("JavaScript");
            engine.eval(js);
            Invocable invoke = (Invocable) engine;
            List<String> p = new ArrayList<>();
            p.add("65");
            p.add("89");
            // [12, 32, ggg]
            Object maxNum = invoke.invokeFunction("decodeToString", p);
            System.out.println(maxNum);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    @Test
    public void test7() {
        String js = "function decodeToString(payload) {" +
                "return String.fromCharCode.apply(String, payload);" +
                "}";
        try {
            ScriptEngineManager factory = new ScriptEngineManager();
            ScriptEngine engine = factory.getEngineByName ("JavaScript");
            engine.eval(js);
            Invocable invoke = (Invocable) engine;
            List<String> p = new ArrayList<>();
            p.add("65");
            p.add("89");
            // [12, 32, ggg]
            Object maxNum = invoke.invokeFunction("decodeToString", p);
            System.out.println(maxNum);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }


    @Test
    public void multiScopes() throws Exception{
        ScriptEngineManager manager = new ScriptEngineManager();
        ScriptEngine engine = manager.getEngineByName("nashorn");
        // 打印全局变量 "x"
        engine.put("x", "hello word!!");
        engine.eval("print(x);");
        // 上面的代码会打印"hello word！！"

        // 现在，传入另一个不同的script context
        ScriptContext context = new SimpleScriptContext();
        //新的Script context绑定ScriptContext的ENGINE_SCOPE
        Bindings bindings = context.getBindings(ScriptContext.ENGINE_SCOPE);

        // 增加一个新变脸到新的范围 engineScope 中
        bindings.put("x", "word hello!!");
        // 执行同一个脚本 - 但这次传入一个不同的script context
        engine.eval("print(x);", bindings);
        engine.eval("print(x);");
    }



    /**
     * 脚本变量
     * @throws Exception
     */
    @Test
    public void scriptVar() throws Exception{
        ScriptEngineManager manager = new ScriptEngineManager();
        ScriptEngine engine = manager.getEngineByName("JavaScript");
        File file = new File("D:\\IdeaProjects\\study\\java-run-js\\src\\test\\java\\com\\salmon\\run\\js\\test.js");
        //将File对象f直接注入到js脚本中并可以作为全局变量使用
        engine.put("files", file);
        engine.eval("print(files.getPath());print(files.getName());");
    }

    @Test
    public void file() throws Exception{
        ScriptEngineManager manager = new ScriptEngineManager();
        ScriptEngine engine = manager.getEngineByName("JavaScript");
        engine.eval(new java.io.FileReader(new File("D:\\IdeaProjects\\study\\java-run-js\\src\\test\\java\\com\\salmon\\run\\js\\test.js")));
        Invocable inv = (Invocable) engine;
        Object obj = engine.get("obj");
        Object res = inv.invokeMethod(obj, "myFun2", "我的名字" );
        System.out.println(res);
    }

    @Test
    public void obj() throws Exception {
        ScriptEngineManager manager = new ScriptEngineManager();
        ScriptEngine engine = manager.getEngineByName("JavaScript");
        //执行这段script脚本
        String script = "var obj = new Object();" +
                "obj.hello = function(name){print('hello, '+name);}";
        engine.eval(script);
        // javax.script.Invocable 是一个可选的接口
        // 检查你的script engine 接口是否已实现!
        // 注意：JavaScript engine实现了Invocable接口
        Invocable inv = (Invocable) engine;
        // 获取我们想调用那个方法所属的js对象
        Object obj = engine.get("obj");
        // 执行obj对象的名为hello的方法
        inv.invokeMethod(obj, "hello", "Script Method !!" );
    }


    /**
     *  使用Script实现java接口
     * @throws Exception
     */
    public void runnableImpl() throws Exception{
        ScriptEngineManager manager = new ScriptEngineManager();
        ScriptEngine engine = manager.getEngineByName("JavaScript");

        // String里定义一段JavaScript代码脚本
        String script = "function run() { print('run called'); }";
        // 执行这个脚本
        engine.eval(script);

        // 从脚本引擎中获取Runnable接口对象（实例）. 该接口方法由具有相匹配名称的脚本函数实现。
        Invocable inv = (Invocable) engine;
        // 在上面的脚本中，我们已经实现了Runnable接口的run()方法
        Runnable runnable = inv.getInterface(Runnable.class);

        // 启动一个线程运行上面的实现了runnable接口的script脚本
        Thread thread = new Thread(runnable);
        thread.start();
    }
    @Test
    public void testRun() throws Exception {
        runnableImpl(); // 另一个线程只从js代码

        Thread.sleep(50);
    }

}
